import numpy as np
from sklearn.metrics.pairwise import cosine_similarity


FLOAT_EPSILON = np.finfo(float).eps
NAN = 'NaN'


def alg_similarity(result_pt, result_ms):
    mean_result_pt = np.mean(result_pt)
    mean_result_ms = np.mean(result_ms)
    similarity = abs(mean_result_pt - mean_result_ms)/mean_result_pt
    if np.isnan(similarity):
        print("Cannot compare by similarity, The net output data contains 0 or nan")
        return NAN
    similarity = 1 - similarity
    return similarity


def alg_cosine_similarity(result_pt, result_ms):
    """
    formula: cos(sitar) = sum(x[i] * y[i]) / (sqrt(sum(x[i] * x[i])) * sqrt(sum(y[i] * y[i])))
    """
    cos_sim = cosine_similarity(result_pt.tolist(), result_ms.tolist())
    return cos_sim[0][0]


def alg_variance(result_pt, result_ms):
    return np.var(result_ms - result_pt)


def alg_max_absolute_error(result_pt, result_ms):
    """
    formula: MaxAE = max(|x[i]-y[i]|)
    """
    max_value = 0.0
    result_pt = result_pt.flatten()
    result_ms = result_ms.flatten()
    for pt_res, ms_res in zip(result_pt, result_ms):
        abs_error = abs(float(pt_res) - float(ms_res))
        if abs_error > max_value:
            max_value = abs_error
    return max_value


def alg_max_relative_error(result_pt, result_ms):
    """
    formula: MaxRE = max|(x[i]-y[i]) / y[i]|
    """
    result_pt = result_pt.flatten()
    result_ms = result_ms.flatten()
    relative_error = np.divide((result_pt - result_ms), result_pt)
    max_relative_error = np.max(np.abs(relative_error))
    if np.isnan(max_relative_error):
        print("Cannot compare by MaxRelativeError, The net output data contains 0 or nan")
        return NAN
    return max_relative_error


def alg_mean_absolute_error(result_pt, result_ms):
    """
    formula: MeanAE = 1/n(|x[1]-y[1]| + |x[2]-y[2]| + ... + |x[i]-y[i]|)
    """
    result_pt = result_pt.flatten()
    result_ms = result_ms.flatten()
    return np.average(np.abs(result_pt - result_ms))


def alg_mean_relative_error(result_pt, result_ms):
    """
    formula: MeanRE = 1/n * (|(x[1]-y[1])/y[1]| + |(x[2]-y[2])/y[2]| + ... + |(x[i]-y[i]|)/y[i]|)
    """
    result_pt = result_pt.flatten()
    result_ms = result_ms.flatten()
    relative_error = np.divide((result_pt - result_ms), result_pt)
    mean_relative_error = np.average(np.abs(relative_error))
    if np.isnan(mean_relative_error):
        print("Cannot compare by MeanRelativeError, The net output data contains 0 or nan")
        return NAN
    return mean_relative_error


def alg_relative_euclidean_distance(result_pt, result_ms):
    """
    formula: sqrt(sum((x[i]-y[i])*(x[i]-y[i]))) / sqrt(sum(y[i]*y[i]))
    """
    result_pt = result_pt.flatten()
    result_ms = result_ms.flatten()
    sum_numerator, sum_denominator = 0.0, 0.0
    for pt_res, ms_res in zip(result_pt, result_ms):
        pt_res = float(pt_res)
        ms_res = float(ms_res)
        sum_numerator += (pt_res - ms_res)**2
        sum_denominator += pt_res * pt_res

    result = '0.0'
    if abs(sum_denominator ** 0.5) > FLOAT_EPSILON and (sum_denominator ** 0.5) != 0:
        result = (sum_numerator ** 0.5) / (sum_denominator ** 0.5)
    return result


def alg_mean_square_error(result_pt, result_ms):
    """
    formula: RMSE = sqrt(((x[0]-y[0])^2 + (x[1]-y[1])^2 + ... + (x[i]-y[i])^2) / n)
    """
    result_pt = result_pt.flatten()
    result_ms = result_ms.flatten()
    mean_square_error = np.sqrt(np.average(((result_pt - result_ms) ** 2)))
    return mean_square_error
